package net.simflow.swf.analyzer.exec.opt;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;

import net.simflow.swf.analyzer.exec.data.ExecParameterPkg;
import net.simflow.swf.analyzer.exec.data.ExecResult;
import net.simflow.swf.analyzer.exec.data.TaskExecResult;
import net.simflow.swf.analyzer.network.data.Flow;
import net.simflow.swf.analyzer.network.data.Network;
import net.simflow.swf.analyzer.network.data.Service;
import net.simflow.swf.analyzer.opt.CusListener;

public class Executor {
	private boolean isExecuted = false;

	private Network network;
	private int totalStep;
	private HashMap<Flow, Integer> flowWeightMap;
	private HashMap<Flow, Integer> flowRelDeadlineMap;
	private HashMap<Service, Integer> serviceAbilityMap;
	private HashMap<Service, Integer> serviceScheduleMap;
	private double normalCreateSpeed;
	private boolean hasPeak;
	private double peakCreateSpeed;
	private int peakA;
	private int peakB;
	private boolean hasValley;
	private double valleyCreateSpeed;
	private int valleyA;
	private int valleyB;

	private CusListener listener;

	private Integer flowWeightSum;
	private int[] flowWeightIntList;
	private Flow[] flowWeightFlowList;

	private int taskCount;

	public Executor(Network network, ExecParameterPkg para) {
		if (network == null || para == null) {
			return;
		}

		if (para.getTotalStep() <= 0) {
			return;
		}
		if (para.getNormalSpeed() <= 0) {
			return;
		}

		Set<Flow> flowSet1 = para.getFlowWeightMap().keySet();
		Collection<Flow> flowSet2 = network.getFlowMap().values();
		if (!flowSet1.containsAll(flowSet2) || !flowSet2.containsAll(flowSet1)) {
			return;
		}

		this.network = network;
		this.totalStep = para.getTotalStep();
		this.serviceAbilityMap = para.getServiceAbilityMap();
		this.serviceScheduleMap = para.getServiceScheduleMap();
		this.flowWeightMap = para.getFlowWeightMap();
		this.flowRelDeadlineMap = para.getFlowRelDeadlineMap();
		this.normalCreateSpeed = para.getNormalSpeed();
		this.hasPeak = para.hasPeak();
		this.peakCreateSpeed = para.getPeakSpeed();
		this.peakA = para.getPeakA();
		this.peakB = para.getPeakB();
		this.hasValley = para.hasValley();
		this.valleyCreateSpeed = para.getValleySpeed();
		this.valleyA = para.getValleyA();
		this.valleyB = para.getValleyB();
		createFlowRanges(flowWeightMap);
	}

	public ExecResult execute() {
		if (isExecuted) {
			return null;
		}

		isExecuted = true;

		if (network == null) {
			return null;
		}

		int time;
		taskCount = 0;
		for (Flow f : network.getFlowMap().values()) {
			f.initExec(serviceAbilityMap, serviceScheduleMap, totalStep,
					flowRelDeadlineMap.get(f));
		}

		TreeMap<Integer, TaskExecResult> taskExecMap = new TreeMap<Integer, TaskExecResult>();
		for (time = 0; time < totalStep; time++) {
			if (listener != null) {
				listener.occur(time);
			}

			for (Flow f : network.getFlowMap().values()) {
				HashSet<Integer> completedTask = f.nextStep(time);
				for (int task : completedTask) {
					taskExecMap.get(task).setEndTime(time);
				}
			}

			Random rnd = new Random();
			int c = 0;
			double y;

			double newSpeed = normalCreateSpeed;
			if (hasPeak) {
				if (time > peakA && time < peakB) {
					newSpeed = peakCreateSpeed;
				}
			}
			if (hasValley) {
				if (time > valleyA && time < valleyB) {
					newSpeed = valleyCreateSpeed;
				}
			}

			if (newSpeed >= 1) {
				c = rnd.nextInt(((int) newSpeed) * 2 + 1);
			}
			y = newSpeed % 1;
			if (y != 0) {
				if (rnd.nextDouble() / y < 1) {
					c++;
				}
			}

			for (int i = 0; i < c; i++) {
				Flow newflow = newFlowTask();
				int taskId = nextTask();
				TaskExecResult res = new TaskExecResult();
				res.setId(taskId);
				res.setFlow(newflow);
				res.setStartTime(time);
				res.setDeadline(time + this.flowRelDeadlineMap.get(newflow));
				taskExecMap.put(taskId, res);
				newflow.newTask(taskId, time);
			}
		}

		ExecResult result = new ExecResult();
		result.setNetwork(this.network);
		result.setTotalStep(this.totalStep);
		result.setFlowWeightMap(this.flowWeightMap);
		result.setFlowRelDeadlineMap(this.flowRelDeadlineMap);
		result.setServiceAbilityMap(this.serviceAbilityMap);
		result.setServiceScheduleMap(this.serviceScheduleMap);
		result.setFlowCreateSpeed(this.normalCreateSpeed);
		result.setTaskExecMap(taskExecMap);

		return result;
	}

	public ExecResult execute(TreeMap<Integer, TaskExecResult> existTaskExecMap) {
		if (existTaskExecMap == null) {
			return null;
		}

		if (isExecuted) {
			return null;
		}

		isExecuted = true;

		if (network == null) {
			return null;
		}

		int time;
		taskCount = 0;
		for (Flow f : network.getFlowMap().values()) {
			f.initExec(serviceAbilityMap, serviceScheduleMap, totalStep,
					flowRelDeadlineMap.get(f));
		}

		TreeMap<Integer, TaskExecResult> taskExecMap = new TreeMap<Integer, TaskExecResult>();
		for (Entry<Integer, TaskExecResult> en : existTaskExecMap.entrySet()) {
			TaskExecResult res = new TaskExecResult();
			res.setId(en.getValue().getId());
			res.setFlow(en.getValue().getFlow());
			res.setStartTime(en.getValue().getStartTime());
			res.setDeadline(en.getValue().getDeadline());
			taskExecMap.put(en.getKey(), res);
			taskExecMap.comparator();
		}

		TaskExecResult[] arr = new TaskExecResult[taskExecMap.size()];
		int cc = 0;
		for (Entry<Integer, TaskExecResult> en : taskExecMap.entrySet()) {
			arr[cc] = en.getValue();
			cc++;
		}

		int currentId;
		for (time = 0; time < totalStep; time++) {
			if (listener != null) {
				listener.occur(time);
			}

			for (Flow f : network.getFlowMap().values()) {
				HashSet<Integer> completedTask = f.nextStep(time);
				for (int task : completedTask) {
					taskExecMap.get(task).setEndTime(time);
				}
			}

			currentId = 0;
			for (int i = currentId; i < arr.length; i++) {
				currentId = i;
				TaskExecResult res = arr[i];
				if (res.getStartTime() > time) {
					break;
				}
				if (res.getStartTime() == time) {
					res.getFlow().newTask(res.getId(), time);
				}
			}
		}

		ExecResult result = new ExecResult();
		result.setNetwork(this.network);
		result.setTotalStep(this.totalStep);
		result.setFlowWeightMap(this.flowWeightMap);
		result.setFlowRelDeadlineMap(this.flowRelDeadlineMap);
		result.setServiceAbilityMap(this.serviceAbilityMap);
		result.setServiceScheduleMap(this.serviceScheduleMap);
		result.setFlowCreateSpeed(this.normalCreateSpeed);
		result.setTaskExecMap(taskExecMap);

		return result;
	}

	private void createFlowRanges(HashMap<Flow, Integer> flowWeightMap) {
		int[] aw = new int[flowWeightMap.size()];
		Flow[] af = new Flow[flowWeightMap.size()];
		int c = 0;
		int d = 0;

		for (Entry<Flow, Integer> en : flowWeightMap.entrySet()) {
			d = d + en.getValue();
			aw[c] = d;
			af[c] = en.getKey();
			c++;
		}

		flowWeightIntList = aw;
		flowWeightFlowList = af;
		flowWeightSum = d;
	}

	private Flow newFlowTask() {
		Integer rd = new Random().nextInt(flowWeightSum);

		for (int i = 0; i < flowWeightIntList.length; i++) {
			if (rd < flowWeightIntList[i]) {
				return flowWeightFlowList[i];
			}
		}

		return null;
	}

	private int nextTask() {
		int c = taskCount;
		taskCount++;
		return c;
	}

	public boolean isExecuted() {
		return isExecuted;
	}

	public void setListener(CusListener listener) {
		this.listener = listener;
	}
}
